
#include <mjin2/mjin2.h>
#include <iostream>

namespace mjin2 {

class Mover : public SequenceListener
{
    public:
        Mover(Timer *timer, SceneNode *node) : mNode(node)
        {
            mSequence = new Sequence(timer);
            mSequence->setListener(this);
            // Setup points.
            float f = 5;
            float rt = 1000;
            SequencePoint p;
            // (0, 0).
            p.push_back(0);
            p.push_back(0);
            mSequence->addPoint(p, 0);
            // (f, f).
            p[0] = f;
            p[1] = f;
            mSequence->addPoint(p, rt);
            // (f, -f).
            p[0] = f;
            p[1] = -f;
            mSequence->addPoint(p, rt);
            // (0, 0).
            p[0] = 0;
            p[1] = 0;
            mSequence->addPoint(p, rt);
            // (-f, f).
            p[0] = -f;
            p[1] = f;
            mSequence->addPoint(p, rt);
            // (-f, -f).
            p[0] = -f;
            p[1] = -f;
            mSequence->addPoint(p, rt);
            // (0, 0).
            p[0] = 0;
            p[1] = 0;
            mSequence->addPoint(p, rt);
        }
        virtual ~Mover()
        {
            mSequence->setListener(0);
            delete mSequence;
        }

        void start()
        {
            mSequence->start();
        }

    private:
        virtual void onSequenceFinish()
        {
            // Restart it with a different interpolation.
            static u32 i = 0;
            ++i;
            if (i > 2)
                i = 0;
            switch (i)
            {
                case 0:
                    mSequence->setInterpolation(SEQUENCE_INTERPOLATION_NONE);
                    break;
                case 1:
                    mSequence->setInterpolation(SEQUENCE_INTERPOLATION_LINEAR);
                    break;
                case 2:
                    mSequence->setInterpolation(SEQUENCE_INTERPOLATION_SPLINE);
                    break;
                default:
                    break;
            }
            start();
        }
        virtual void onSequencePoint(const SequencePoint &p)
        {
            mNode->setPosition(Vec3(p[0], mNode->position().y, p[1]));
        }

        Sequence *mSequence;
        SceneNode *mNode;
};

class Rotator : public TimerListener
{
    public:
        Rotator(Timer *timer, SceneNode *node) : mTimer(timer),
                                                 mNode(node)
        {
            mTimer->addListener(this, 0);
        }
        virtual ~Rotator()
        {
            mTimer->removeListener(this);
        }

        virtual void onTimerTick(u32 millisecondsElapsed)
        {
            float angle = static_cast<float>(millisecondsElapsed) / 30;
            Vec4 r = rotationSum(mNode->rotation(),
                                 degreeToQuaternion(Vec3(0, angle, 0)));
            mNode->setRotation(r);
        }

    private:
        Timer *mTimer;
        SceneNode *mNode;
};

class Test : public InputListener,
             public WindowListener
{
    public:
        Test(const String &dataDir) throw (Exception): mDataDir(dataDir)
        {
            mApp = new Application("MJIN2", "log.html");
            WindowConfig wc(640, 480, false);
            mWnd = new Window("MJIN2", wc);
            mWnd->addInputListener(this);
            mWnd->addWindowListener(this);
            mTimer = new Timer;
            mWnd->setTimer(mTimer);
            // Scene.
            PathResolver *resolver = mApp->pathResolver();
            resolver->addSearchPath(mDataDir);
            mSceneNodeFactory = new SceneNodeFactory;
            mScene = new Scene;
            Camera *cam = mWnd->camera();
            cam->setPosition(Vec3(0, -60, 0));
            cam->setRotation(degreeToQuaternion(Vec3(90, 5, 0)));
            mSceneNodeSelector = new SceneNodeSelector;
            mSceneNodeSelector->setCamera(cam);
            mWnd->setScene(mScene);
            mTruck =
                mSceneNodeFactory->createMesh("truck",
                                              resolver->findFile("dumptruck.osgt"));
            mSceneNodeSelector->addNode(mTruck);
            mBox = mSceneNodeFactory->createMesh("box",
                                                 resolver->findFile("box.osgt"),
                                                 resolver->findFile("textures/wall.png"));
            mSceneNodeSelector->addNode(mBox);
            mScene->addChild(mBox);
            mScene->addChild(mTruck);
            mTruck->setPosition(Vec3(15, 0, 0));
            mTruck->setRotation(rotationSum(degreeToQuaternion(Vec3(90, 45, 0)),
                                            degreeToQuaternion(Vec3(0, 45, 0))));
            // Absolute position.
            Vec3 p = mTruck->positionAbs();
            std::cout << MJIN2_STR("a. truck abs pos: %f %f %f\n", p.x, p.y, p.z);
            mBox->setPosition(Vec3(10, 0, 0));
            p = mTruck->positionAbs();
            std::cout << MJIN2_STR("b. truck abs pos: %f %f %f\n", p.x, p.y, p.z);
            // Absolute rotation.
            Vec4 r = mTruck->rotationAbs();
            std::cout << MJIN2_STR("a. truck abs rot: %f %f %f %f\n", r.x, r.y, r.z, r.w);
            mBox->setRotation(degreeToQuaternion(Vec3(0, 45, 0)));
            r = mTruck->rotationAbs();
            std::cout << MJIN2_STR("b. truck abs rot: %f %f %f %f\n", r.x, r.y, r.z, r.w);
            SceneMesh *table =
                mSceneNodeFactory->createMesh("table",
                                              resolver->findFile("box.osgt"));
            table->setTexture(resolver->findFile("textures/wall.png"));
            mSceneNodeSelector->addNode(table);
            mScene->addChild(table);
            table->setScale(Vec3(8, 1, 6));
            table->setPosition(Vec3(0, 10, -5));
            table->setRotation(degreeToQuaternion(Vec3(25, 0, 0)));
            mRotator = new Rotator(mTimer, table);
            mMover   = new Mover(mTimer, table);
            mMover->start();
            Light *light = mScene->light();
            light->setPosition(Vec4(4, -20, 5, 0));
            light->setAmbient(Vec4(0.2, 0.2, 0.2, 1));
            light->setDiffuse(Vec4(0.8, 0.8, 0.8, 1));
            SceneLine *line = mSceneNodeFactory->createLine("line");
            Vec3List verts;
            verts.push_back(Vec3(0,  -30, 0));
            verts.push_back(Vec3(10, -30, 0));
            verts.push_back(Vec3(5,  -30, 5));
            line->setColor(Vec4(1, 0, 0, 1));
            line->setVertices(verts);
            mScene->addChild(line);
        }
        ~Test() throw ()
        {
            delete mMover;
            delete mRotator;
            mWnd->setTimer(0);
            delete mTimer;
            mWnd->removeInputListener(this);
            mWnd->removeWindowListener(this);
            mWnd->setScene(0);
            delete mSceneNodeFactory;
            delete mSceneNodeSelector;
            delete mScene;
            delete mWnd;
            delete mApp;
        }

        virtual void onInput(const InputEvent &e) throw ()
        {
            if (e.press)
                switch (e.input)
                {
                    case INPUT_KEY_1:
                        {
                            String title = mWnd->title();
                            if (title == "MJIN2")
                                title = "MJIN2 title change";
                            else
                                title = "MJIN2";
                            mWnd->setTitle(title);
                        }
                        break;
                    case INPUT_KEY_2:
                        {

                            WindowConfig wc = mWnd->config();
                            if (wc.width == 640)
                            {
                                wc.width  = 800;
                                wc.height = 600;
                            }
                            else
                            {
                                wc.width  = 640;
                                wc.height = 480;
                            }
                            std::cout << MJIN2_STR("resolution: %ux%u\n",
                                                   wc.width,
                                                   wc.height);
                            mWnd->setConfig(wc);
                        }
                        break;
                    case INPUT_KEY_3:
                        {
                            WindowConfig wc = mWnd->config();
                            wc.fullScreen = !wc.fullScreen;
                            std::cout << "full screen: " << wc.fullScreen << std::endl;
                            mWnd->setConfig(wc);
                        }
                        break;
                    case INPUT_KEY_4:
                        {
                            WindowConfig wc = mWnd->config();
                            wc.vsync = !wc.vsync;
                            std::cout << "vsync: " << wc.vsync << std::endl;
                            mWnd->setConfig(wc);
                        }
                        break;
                    case INPUT_KEY_5:
                        {
                            WindowConfig wc = mWnd->config();
                            if (wc.antiAliasing == 0)
                                wc.antiAliasing = 2;
                            else
                                wc.antiAliasing *= 2;
                            if (wc.antiAliasing > 16)
                                wc.antiAliasing = 0;
                            std::cout << "AA: "
                                      << static_cast<int>(wc.antiAliasing)
                                      << std::endl;
                            mWnd->setConfig(wc);
                        }
                        break;
                    case INPUT_KEY_6:
                        {
                            WindowConfig wc = mWnd->config();
                            if (wc.fps == 0)
                                wc.fps = 60;
                            else
                                wc.fps -= 10;
                            std::cout << "FPS: " << static_cast<int>(wc.fps) << std::endl;
                            mWnd->setConfig(wc);
                        }
                        break;
                    case INPUT_KEY_ESCAPE:
                        std::cout << "Quit\n";
                        mWnd->stop();
                        break;
                    case INPUT_KEY_X:
                        {
                            Vec3 p = mWnd->camera()->position();
                            std::cout << MJIN2_STR("cam pos: %f %f %f\n", p.x, p.y, p.z);
                            std::cout << MJIN2_STR("Key X %u pressed\n", e.input);
                            mWnd->camera()->setPosition(Vec3(0, -60, 0));
                            mWnd->camera()->setRotation(degreeToQuaternion(Vec3(90, 0, 0)));
                            Vec4 g = mWnd->geometry();
                            mWnd->setMousePosition(g.z / 2, g.w / 2);
                        }
                        break;
                    case INPUT_KEY_B:
                        {
                            Vec4 r = rotationSum(mBox->rotation(),
                                                 degreeToQuaternion(Vec3(0, 10, 0)));
                            mBox->setRotation(r);
                            std::cout << MJIN2_STR("mBox->setRotation(%f, %f, %f, %f)\n",
                                                   r.x, r.y, r.z, r.w);
                            Vec3 p = mTruck->positionAbs();
                            std::cout << MJIN2_STR("truck abs pos: %f %f %f\n", p.x, p.y, p.z);
                            r = mTruck->rotationAbs();
                            std::cout << MJIN2_STR("truck abs rot: %f %f %f %f\n", r.x, r.y, r.z, r.w);
                        }
                        break;
                    case INPUT_KEY_T:
                        {
                            Vec4 r = rotationSum(mTruck->rotation(),
                                                 degreeToQuaternion(Vec3(0, 10, 0)));
                            mTruck->setRotation(r);
                            std::cout << MJIN2_STR("mTruck->setRotation(%f, %f, %f, %f)\n",
                                                   r.x, r.y, r.z, r.w);
                        }
                        break;
                    case INPUT_MOUSE_BUTTON_LEFT:
                    case INPUT_MOUSE_BUTTON_MIDDLE:
                    case INPUT_MOUSE_BUTTON_RIGHT:
                        {
                            std::cout << "Mouse button. X: " << e.x << " Y: " << e.y << std::endl;
                            SceneNode *n = mSceneNodeSelector->select(e.x, e.yi);
                            if (n)
                                std::cout << "Selected node name: " << n->name() << std::endl;
                            else
                                std::cout << "No valid node returned\n";
                        }
                        break;
                    case INPUT_SCROLL_UP:
                        std::cout << "Scroll up\n";
                        break;
                    case INPUT_SCROLL_DOWN:
                        std::cout << "Scroll down\n";
                        break;
                    default:
                        std::cout <<
                            MJIN2_STR("Unhandled input. Input: %d UnmodifiedInput: %d X: %d Y: %d Press: %d\n",
                                      e.input,
                                      e.unmodifiedInput,
                                      e.x,
                                      e.y,
                                      e.press);
                        break;
                }
            else
                std::cout <<
                    MJIN2_STR("Unhandled input. Input: %d UnmodifiedInput: %d X: %d Y: %d Press: %d\n",
                              e.input,
                              e.unmodifiedInput,
                              e.x,
                              e.y,
                              e.press);
        }
        virtual void onWindowClose() throw ()
        {
            std::cout << "Quit\n";
        }
        virtual void onWindowRecreation() throw ()
        {
            VideoInfo vi = mWnd->videoInfo();
            std::cout << videoInfoToStr(vi);
            mScene->recreateShadows();
        }
        void run()
        {
            mWnd->run();
        }

    private:
        Application *mApp;
        Window *mWnd;
        SceneNodeFactory *mSceneNodeFactory;
        Scene *mScene;
        String mDataDir;
        SceneMesh *mBox;
        SceneMesh *mTruck;
        SceneNodeSelector *mSceneNodeSelector;
        Timer *mTimer;
        Rotator *mRotator;
        Mover *mMover;
};

} // namespace mjin2

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cout << MJIN2_STR("Usage: %s /path/to/t/mjin2\n", argv[0]);
        return 0;
    }
    mjin2::String dataDir = argv[1];
    dataDir += "/MJIN2";
    mjin2::Test *t = new mjin2::Test(dataDir);
    t->run();
    delete t;
    return 0;
}

